// Copyright (C) 2022 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
//
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<广西木子科技有限公司>，授权编号：<NT220402151538>，代码指纹：<A.648883738.885>，技术对接人微信：<ID: Lingmuziyi>】
// 【授权寄送：<收件：李先生、地址：南宁市科园西十路11号国电智能大厦1101F、电话：17736659550、邮箱：yingshashou@vip.qq.com>】
//
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
//
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.x52im.rainbowchat.http.logic;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import javax.imageio.ImageIO;

import com.eva.epc.common.util.CommonUtils;
import com.eva.framework.dbpool.DBShell;
import com.eva.framework.utils.LoggerFactory;
import com.x52im.rainbowchat.BaseConf;

/**
 * 群组头像生成辅助类（生成逻辑与微信保持一致：4人以内生成4宫格、9人以内生成9宫格等）。
 * <p>
 * <b>关于性能的说明：</b>
 * 因为生成群头像时，需要读取群员头像文件并写入最络群组头像文件，因而在用户量较大时，
 * 这些磁盘操作都有可能带来性能瓶颈，所以建议如果是性能优先的im系统里可以考虑不要自
 * 动后成群头像，否则请评估并考虑将这样的功能拆分成独立的服务，从而不至于影响主业
 * 务服务器的性能.
 *
 * @author chuan_bai(https : / / www.jianshu.com / p / 394f9b0357b3)
 * @author Jack Jiang
 * @since 4.3
 */
public final class GroupAvatarHelper {
    /**
     * 图片的间隙
     */
    private static final int SIDE = 6;
    /**
     * 画板尺寸
     */
    private static final int CANVANS_W = 112;
    private static final int CANVANS_H = 112;

    /**
     * 尺寸1 （小）
     */
    private static final int ONE_IMAGE_SIZE = CANVANS_H - (2 * SIDE);
    /**
     * 尺寸2 （中）
     */
    private static final int TWO_IMAGE_SIZE = (CANVANS_H - (3 * SIDE)) / 2;
    /**
     * 尺寸3 （大）
     */
    private static final int FIVE_IMAGE_SIZE = (CANVANS_H - (4 * SIDE)) / 3;

    private static BufferedImage defaultGroupMemberAvatar = null;

    /**
     * 一站式生成一个群组的群头像（本方法将自动查询该群内的前9个成员头像并拼合出完整群头像的全部逻辑）。
     *
     * @param groupId 要生成群头像的群组id
     * @return true表示生成成功，否则表示过程中出错了（没有成功生成）
     * @see #getCombinationOfhead
     */
    public static boolean generateGroupAvatar(String groupId) {
        LoggerFactory.getLog().debug("【生成群聊头像-START】群组" + groupId + "的头像生成马上开始。。。。。。");

        long t = System.currentTimeMillis();

        boolean sucess = false;
        try {
            // 当上次拼合群头像时，使用的群员头像数
            String[] cnts = GroupChatProcessor.db.querySingleRowItems(
                    "select g_member_count,avatar_include_cnt from group_base" + " where g_id='" + groupId + "'");

            int current_group_member_count = CommonUtils.getIntValue(cnts[0], 0);
            int last_avatar_include_cnt = CommonUtils.getIntValue(cnts[1], 0);
            // 当当前群成员大于9 且 上次拼合群成员头像时用的头像数已经足9时，就不需要重新
            // 生成群头像了，这样就能节省服务端的资源（不然反复生成群头像，很不经济）
            if (current_group_member_count > 9 && last_avatar_include_cnt >= 9) {
                LoggerFactory.getLog().debug("【生成群聊头像-END】群组" + groupId
                        + "的头像无需再次生成，原因：（当前群员总数=" + current_group_member_count
                        + ", 上次拼合使用头像数=" + last_avatar_include_cnt + "）。");
                return false;
            }

            // 当群不足9人 或上次拼合群成员头像时用的头像数不足9人时，就需
            // 要重新生成群头像，从而保证群头像能与当前实际的群人数保持一致。
            // 总之生成原则就是：当群小于9人或上次拼合群成员头像时用的头像数不足9人时，就重新生新群头像！

            LoggerFactory.getLog().debug("【生成群聊头像-continue】群组" + groupId
                    + "的头像将继续生成>>> ，原因：（当前群员总数=" + current_group_member_count
                    + ", 上次拼合使用头像数=" + last_avatar_include_cnt + "）。");

            StringBuilder sb = new StringBuilder()
                    .append("select ");

            // SQLServer数据库中取前9个用户数据的方法（即在SQL语句前面加上top语句）
            if (DBShell.isSQLServer())
                sb.append(" top 9 ");

            sb.append("	 b.user_avatar_file_name ")
                    .append("FROM ")
                    /** 使用子查询提升查询性能 */
                    .append("(")
                    .append("	select user_uid,g_id,nickname_ingroup from group_members where g_id='" + groupId + "' ")
                    .append(") a ")
                    .append("left join missu_users b on a.user_uid=b.user_uid ")
                    .append("order by b.user_avatar_file_name desc ");

            // MySQL数据库中取前9个用户数据的方法（即在SQL语句末尾加上linit语句）
            if (DBShell.isMySQL())
                sb.append(" limit 0,9 ");

            // 查询该群中9个用户的头像文件名（不足9个则返回实际数据
            // ，大于9个的群则尽最大可能返回排序后的9个有头像的人）
            Vector<Vector> originalVecs = GroupChatProcessor.db.queryData(sb.toString());

            LoggerFactory.getLog().debug("【生成群聊头像-第1/2步】群组" + groupId + "的前9个用户查询完成(实取数量：" + originalVecs.size() + "人)，此步共耗时：" + (System.currentTimeMillis() - t) + "毫秒。");

            if (originalVecs.size() > 0) {
                ArrayList<String> al = new ArrayList<String>();
                for (Vector r : originalVecs) {
                    String avartarFileName = (String) r.get(0);
                    al.add(avartarFileName);
                }

                // 开始拼合头像并保存到磁盘
                boolean combinateSucess = GroupAvatarHelper.getCombinationOfhead(al, groupId);

                if (combinateSucess) {
                    try {
                        // 更新数据中本次拼合头像的人数
                        GroupChatProcessor.db.update("update group_base set avatar_include_cnt="
                                + al.size() + " where g_id=?", new Object[]{groupId}, true);
                    } catch (Exception e) {
                        LoggerFactory.getLog().warn(e.getMessage(), e);
                    }
                }

                sucess = combinateSucess;
            } else {
                LoggerFactory.getLog().warn("【生成群聊头像】群组" + groupId + "的群员数是0，无需生成群头像！");
            }
        } catch (Exception e) {
            LoggerFactory.getLog().warn("【群聊头像】生成群" + groupId + "的头像时出错了", e);
        }

        LoggerFactory.getLog().debug("【生成群聊头像-END】群组" + groupId + "的头像生成已完成（成功?"
                + sucess + "），两步总耗时共：" + (System.currentTimeMillis() - t) + "毫秒。");

        return sucess;
    }

    /**
     * 获得默认的成员头像（这是在极端情况下读不到真正的头像文件时可以使用）。
     *
     * @return
     */
    private static BufferedImage getDefaultGroupMemberAvatar() {
        if (defaultGroupMemberAvatar == null) {
            try {
                // 缓存起提高性能
                defaultGroupMemberAvatar = ImageIO.read(
                        GroupAvatarHelper.class.getResourceAsStream("resource/default_group_member_avatar.png"));
            } catch (Exception e) {
                LoggerFactory.getLog().warn(e.getMessage(), e);
            }
        }
        return defaultGroupMemberAvatar;
    }

    /**
     * 用指定的用户头像文件集合，拼合出最终的RainbowChat的群组头像。
     *
     * @param membersAvatarFileNames 群员的头像文件链接
     * @param dir                    输出路径
     * @param groupId                群编号
     * @return
     * @throws IOException
     */
    private static boolean getCombinationOfhead(List<String> membersAvatarFileNames
            , String groupId) throws Exception {
        LoggerFactory.getLog().debug("【生成群聊头像-第2/2步】群组" + groupId + "的群头像拼合马上开始 ....");

        long t = System.currentTimeMillis();

        List<BufferedImage> bufferedImages = new ArrayList<BufferedImage>();

        int imageSize = 0;
        if (membersAvatarFileNames.size() <= 1) {
            //若为一张图片
            imageSize = ONE_IMAGE_SIZE;
        } else if (membersAvatarFileNames.size() > 1 && membersAvatarFileNames.size() < 5) {
            //若为2-4张图片

            imageSize = TWO_IMAGE_SIZE;
        } else {
            //若>=5张图片
            imageSize = FIVE_IMAGE_SIZE;
        }

        for (int i = 0; i < membersAvatarFileNames.size(); i++) {
            // 本次要读取的图片路径
            String path = null;

            String avatarFileName = membersAvatarFileNames.get(i);

            // 如果传进来的头像为空，则表示将使用默认头像，resize2方法中会自动处理
            if (!CommonUtils.isStringEmpty(avatarFileName, true)) {
                // 头像缩略图文件路径
                String avatarThumbFilePath = BaseConf.getInstance().getDIR_USER_AVATAR_UPLOAD() + "th_" + avatarFileName;
                // 头像文件路径
                String avatarFilePath = BaseConf.getInstance().getDIR_USER_AVATAR_UPLOAD() + avatarFileName;
                // 优先使用头像缩略图来拼合群聊头像，这样能提高服务端性能和减少资源占用，
                // 只在缩略图不存在时使用头像文件原图（不存在缩略图的情况可能是RainbowChat4.3
                // 以前的系统中没有生成用户头像，也可能是用户头像上传时没有成功生成缩略图等）
                if (!new File(avatarThumbFilePath).exists())
                    path = avatarFilePath;
                else
                    path = avatarThumbFilePath;
            }

            BufferedImage resize2 = GroupAvatarHelper.resize2(path, imageSize, imageSize, true);
            bufferedImages.add(resize2);
        }

        BufferedImage outImage = new BufferedImage(CANVANS_W, CANVANS_H, BufferedImage.TYPE_INT_RGB);

        // 生成画布
        Graphics g = outImage.getGraphics();
        Graphics2D g2d = (Graphics2D) g;
        // 设置背景色
        g2d.setBackground(new Color(219, 223, 225));
        // 通过使用当前绘图表面的背景色进行填充来清除指定的矩形。
        g2d.clearRect(0, 0, CANVANS_W, CANVANS_H);

        // 开始拼凑 根据图片的数量判断该生成那种样式的组合头像目前为九种
        for (int i = 1; i <= bufferedImages.size(); i++) {
            Integer size = bufferedImages.size();

            switch (size) {
                case 1: {
                    g2d.drawImage(bufferedImages.get(i - 1), SIDE, SIDE, null);
                    break;
                }
                case 2: {
                    if (i == 1)
                        g2d.drawImage(bufferedImages.get(i - 1), SIDE, (CANVANS_W - imageSize) / 2, null);
                    else
                        g2d.drawImage(bufferedImages.get(i - 1), 2 * SIDE + imageSize, (CANVANS_W - imageSize) / 2, null);

                    break;
                }
                case 3: {
                    if (i == 1)
                        g2d.drawImage(bufferedImages.get(i - 1), (CANVANS_W - imageSize) / 2, SIDE, null);
                    else
                        g2d.drawImage(bufferedImages.get(i - 1), (i - 1) * SIDE + (i - 2) * imageSize, imageSize + (2 * SIDE), null);

                    break;
                }
                case 4: {
                    if (i <= 2)
                        g2d.drawImage(bufferedImages.get(i - 1), i * SIDE + (i - 1) * imageSize, SIDE, null);
                    else
                        g2d.drawImage(bufferedImages.get(i - 1), (i - 2) * SIDE + (i - 3) * imageSize, imageSize + 2 * SIDE, null);

                    break;
                }
                case 5: {
                    if (i <= 2)
                        g2d.drawImage(bufferedImages.get(i - 1), (CANVANS_W - 2 * imageSize - SIDE) / 2 + (i - 1) * imageSize + (i - 1) * SIDE, (CANVANS_W - 2 * imageSize - SIDE) / 2, null);
                    else
                        g2d.drawImage(bufferedImages.get(i - 1), (i - 2) * SIDE + (i - 3) * imageSize, ((CANVANS_W - 2 * imageSize - SIDE) / 2) + imageSize + SIDE, null);

                    break;
                }
                case 6: {
                    if (i <= 3)
                        g2d.drawImage(bufferedImages.get(i - 1), SIDE * i + imageSize * (i - 1), (CANVANS_W - 2 * imageSize - SIDE) / 2, null);
                    else
                        g2d.drawImage(bufferedImages.get(i - 1), ((i - 3) * SIDE) + ((i - 4) * imageSize), ((CANVANS_W - 2 * imageSize - SIDE) / 2) + imageSize + SIDE, null);

                    break;
                }
                case 7: {
                    if (i <= 1)
                        g2d.drawImage(bufferedImages.get(i - 1), 2 * SIDE + imageSize, SIDE, null);

                    if (i <= 4 && i > 1)
                        g2d.drawImage(bufferedImages.get(i - 1), ((i - 1) * SIDE) + ((i - 2) * imageSize), 2 * SIDE + imageSize, null);

                    if (i <= 7 && i > 4)
                        g2d.drawImage(bufferedImages.get(i - 1), ((i - 4) * SIDE) + ((i - 5) * imageSize), 3 * SIDE + 2 * imageSize, null);

                    break;
                }
                case 8: {
                    if (i <= 2)
                        g2d.drawImage(bufferedImages.get(i - 1), (CANVANS_W - 2 * imageSize - SIDE) / 2 + (i - 1) * imageSize + (i - 1) * SIDE, SIDE, null);

                    if (i <= 5 && i > 2)
                        g2d.drawImage(bufferedImages.get(i - 1), ((i - 2) * SIDE) + ((i - 3) * imageSize), 2 * SIDE + imageSize, null);

                    if (i <= 8 && i > 5)
                        g2d.drawImage(bufferedImages.get(i - 1), ((i - 5) * SIDE) + ((i - 6) * imageSize), 3 * SIDE + 2 * imageSize, null);

                    break;
                }
                case 9: {
                    if (i <= 3)
                        g2d.drawImage(bufferedImages.get(i - 1), (i * SIDE) + ((i - 1) * imageSize), SIDE, null);

                    if (i <= 6 && i > 3)
                        g2d.drawImage(bufferedImages.get(i - 1), ((i - 3) * SIDE) + ((i - 4) * imageSize), 2 * SIDE + imageSize, null);

                    if (i <= 9 && i > 6)
                        g2d.drawImage(bufferedImages.get(i - 1), ((i - 6) * SIDE) + ((i - 7) * imageSize), 3 * SIDE + 2 * imageSize, null);

                    break;
                }
                default:
                    break;
            }
        }

        // 拼合后的群聊头像保存路径
        StringBuffer outPath = new StringBuffer().append(BaseConf.getInstance().getDIR_USER_AVATARGROUP_DIR())
                .append(groupId).append(".jpg"); // 生成的群组图片名形如”000000001.jpg“

        String format = "JPG";
        File file = new File(outPath.toString());
        if (!file.exists()) {
            file.mkdirs();
        }

        // 保存群头像
        boolean sucess = ImageIO.write(outImage, format, file);

        LoggerFactory.getLog().debug("【群聊头像-第2/2步】群组" + groupId + "的头像拼合完毕(成功了?" + sucess + ")，此步共耗时：" + (System.currentTimeMillis() - t) + "毫秒。");

        return sucess;
    }

    /**
     * 图片缩放。
     *
     * @param filePath 图片路径（支持网络图片）
     * @param height   高度
     * @param width    宽度
     * @param bb       比例不对时是否需要补白
     */
    private static BufferedImage resize2(String filePath, int height, int width, boolean bb) {
        try {
            double ratio = 0; // 缩放比例
//          System.out.println("图片缩放"+filePath);
            BufferedImage bi = null;

            // 进行容错，保证在读不到图片数据时余下代码还能按照正常算法走完
            if (!CommonUtils.isStringEmpty(filePath, true)) {
                try {
                    if (filePath.indexOf("http://") == 0)
                        bi = ImageIO.read(new URL(filePath));
                    else
                        bi = ImageIO.read(new File(filePath));
                } catch (Exception e) {
                    LoggerFactory.getLog().warn(e.getMessage() + "(" + filePath + ")", e);
                }
            }

            // 极端情况下读不到图片数据的话，至少给个默认的
            if (bi == null)
                bi = getDefaultGroupMemberAvatar();

            Image itemp = bi.getScaledInstance(width, height, Image.SCALE_SMOOTH);
            // 计算比例
            if ((bi.getHeight() > height) || (bi.getWidth() > width)) {
                if (bi.getHeight() > bi.getWidth())
                    ratio = (new Integer(height)).doubleValue() / bi.getHeight();
                else
                    ratio = (new Integer(width)).doubleValue() / bi.getWidth();

                AffineTransformOp op = new AffineTransformOp(AffineTransform.getScaleInstance(ratio, ratio), null);
                itemp = op.filter(bi, null);
            }
            if (bb) {
                BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
                Graphics2D g = image.createGraphics();
                g.setColor(Color.white);
                g.fillRect(0, 0, width, height);

                if (width == itemp.getWidth(null)) {
                    g.drawImage(itemp, 0, (height - itemp.getHeight(null)) / 2,
                            itemp.getWidth(null), itemp.getHeight(null),
                            Color.white, null);
                } else {
                    g.drawImage(itemp, (width - itemp.getWidth(null)) / 2, 0,
                            itemp.getWidth(null), itemp.getHeight(null),
                            Color.white, null);
                }
                g.dispose();
                itemp = image;
            }
            return (BufferedImage) itemp;
        } catch (Exception e) {
            LoggerFactory.getLog().warn(e.getMessage() + "(" + filePath + ")", e);
        }

        return null;
    }

//	/**
//	 * 可依次生成九种情况的群头像
//	 *
//	 * @param args
//	 * @throws IOException
//	 */
//	public static void main(String[] args) throws Exception
//	{
//		DBShell.DB_TYPE = DBShell.DBType.mySql;
//
//		String groupId = "0000000155";
//
////		List<String> list = new ArrayList<String>();
////		list.add("400070_79196464a7a8bd4103eff494e0d4cd67.jpg");
////		list.add("a.jpg");
////		list.add("400092_4f330a9bcf1d22054364c119ceb8b753.jpg");
////		list.add(null);
////		list.add("400088_1d88c9fea46d09692828b5251f420db0.jpg");
////		list.add("");
////		list.add("400096_76dfc2ab1f57af24a495b25dce1395dc.jpg");
////		list.add("400095_c2ea8d7a74a96b329b5d84cd21208f9d.jpg");
////		GroupAvatarHelper.getCombinationOfhead(list, groupId.toString());
//
//		GroupAvatarHelper.generateGroupAvatar(groupId);
//	}
}
